#!/usr/bin/perl
package TEdit::UI::Main;

use strict;
use warnings qw( all );
use lib ( 
	'./lib',  
	$ENV{'HOME'}.'/lib/perl', 
	$ENV{'HOME'}.'/lib' 
);

use TEdit::MediaFile;
use Curses::UI;
use Cwd;
use File::Find::Rule;
use Data::Dumper;
use Audio::FLAC::Header;
use File::Basename;
use File::Copy;

our $si_units = 1; # change to false for KiB/MiB/etc
our $safety_net = 0; # change to false to not backup files
our $debug = 1;

# program internals
our $files;
our $state;
our $cui;
=head1 VERSION

Version 1001

=cut
our $VERSION = '1001';

=head1 TODO

=item genre popup selector
=item batch editor
=item file renamer

=cut

sub main {
	
	$cui = Curses::UI->new( 
		-clear_on_exit => 1,
		-debug => 0,
		-color_support => 1,
	);

	$state->{root} = $cui->add(
		'root', 'Window',
		-title => $0,
	);
	
	$state->{temp}{rows} = $state->{root}->height;
	$state->{temp}{cols} = $state->{root}->width;
	
	fileselect_cwd();

	op_mm();

	
	$cui->set_binding( \&my_exit, "\cC" );
	$cui->mainloop();
}

=head1 op_mm()

This is the main menu function, called when initializing or 
returning to the main menu.

It has two main panes, the filelist and the filesummary.

It also has the menubar, for invoking sub-functions.  All
sub-functions return here when finished.

=cut

sub op_mm {

	$state->{mainwin} = $state->{root}->add(
		'mainwin', 'Window',
		-border => 0,
	);

	$state->{menubar} = $state->{mainwin}->add(
		'menubar', 'Menubar',
		-fg => 'blue',
		-menu => _menubar(),
	);

	$state->{menubar}->set_binding( \&my_exit, Curses::UI::Common::CUI_ESCAPE() );
	$state->{menubar}->set_binding( \&my_exit, "\cC" );
	$state->{menubar}->set_binding( \&my_exit, "q" );
	$state->{menubar}->set_binding( \&my_exit, "x" );
	$state->{menubar}->focus();


	$state->{filelist} = $state->{mainwin}->add(
		'filelist', 'Listbox',
		-values => files_displaynames(),
		-width => 45,
		-height => $state->{mainwin}->height() - 2,
		-padtop => 1,
		-ipadtop => 1,
		-border => 1,
		-title => 'File Names',
		-vscrollbar => 'right',
		-fg => 'magenta',
		-bg => 'white',
		-onchange => sub {
			$state->{temp}{edit_index} = 
				$state->{filelist}->get_active_id();
			$state->{filelist}->clear_selection();
			op_edit_tags();
		},
	);

	$state->{mainwin}->delete('filesummary');

	$state->{filesummary} = $state->{mainwin}->add(
		'filesummary', 'Label',
		-x => $state->{filelist}->width() ,
		-height => $state->{mainwin}->height() - 2,
		-padtop => 1,
		-title => 'Summary',
		-border => 1,
		-fg => 'magenta',
		-text => sprintf(
			"\n".
			"File Count: %u\n".
			"Total File Size: %s\n",
			0+@$files,
			size_prettyprint(_selection_size()),
		),
		-padright => 1,
	);

	$state->{filelist}->set_binding(
		sub { my_exit(); },
		Curses::UI::Common::CUI_ESCAPE()
	);

	$state->{menubar}->focus();

	Curses::UI::doupdate;

}

sub op_batch_edit {
	
	# todo fixme make a close_windows() sub that finds children of root
	$state->{root}->delete('mainwin');
	$state->{root}->delete('editwin');
	$state->{root}->delete('batcheditwin');

	$state->{batcheditwin} = $state->{root}->add(
		'batcheditwin', 'Window',
		-title => "Batch Editor",
		-border => 1,
		-pad => 1,
		-bg => 'blue',
		-fg => 'white',
	);

	$state->{batcheditwin}->draw();

	$state->{batchedittagvalue} = $state->{batcheditwin}->add(
		'batchedittagvalue', 'TextEditor',
		-title => 'Value',
		-singleline => 1,
		-padright => 3,
		-border => 1,
		-bg => 'blue',
		-fg => 'white',
		-x => 1,
		-y => 4,
	);
	$state->{batchedittagvalue}->draw();

	$state->{batchedittagselect} = $state->{batcheditwin}->add(
		'batchedittagselect', 'TextEditor',
		-title => 'Tag Name',
		-singleline => 1,
		-padright => 3,
		-border => 1,
		-bg => 'blue',
		-fg => 'white',
		-onfocus => \&batch_edit_taglist_popup,
		-x => 1,
		-y => 1,
	);
	$state->{batchedittagselect}->draw();

	$state->{batcheditwin}->set_binding(
		sub {
			$state->{root}->delete('batcheditwin'); 
			op_mm();
		}, 
		Curses::UI::Common::CUI_ESCAPE()
	);

	batch_edit_taglist_popup();
}

sub batch_edit_taglist_popup {

	$state->{batcheditwin}->delete('batchedittaglistwin');
	$state->{batchedittaglistwin} = $state->{batcheditwin}->add(
		'batchedittaglistwin', 'Window',
		-intellidraw => 0,
		-title => 'Select Tag',
		-border => 1,
		-x => 0,
		-y => 0,
		-padtop => 2,
		-padleft => 5,
		-width => 40,
		-height => 15,
	);

	my $ftn = _flac_tag_names();  # fixme make this pull tag list from TEdit::MediaFile
	$state->{batchedittaglistbox} = $state->{batchedittaglistwin}->add(
		'batchedittaglistbox', 'Listbox',
		-values => [ sort keys(%$ftn) ],
		-labels => $ftn,
		-bg => 'blue',
		-fg => 'white',
		-x => 0,
		-y => 0,
		-radio => 1,
		-onchange => sub {
			$state->{temp}{batch_tag_name} = $_[0]->get();
			# this is a closure as we're referencing $ftn above
			$state->{temp}{batch_tag_display} = 
				$ftn->{$state->{temp}{batch_tag_name}};
			$state->{batchedittagselect}->text( 
				$state->{temp}{batch_tag_display}
			);
			$state->{batchedittaglistbox}->DESTROY();
			$state->{batchedittaglistwin}->DESTROY();
			$state->{batchedittagvalue}->focus();
		},

	);
	$state->{batchedittaglistbox}->draw();
	$state->{batchedittaglistbox}->focus();
}

sub _flac_tag_names {
	{
		'TITLE'      => 'Title', 
		'ALBUM'      => 'Album',
		'ARTIST'     => 'Artist',
		'DATE'       => 'Date',
		'ALBUMARTIST'=> 'Album Artist',
		'GENRE'      => 'Genre',
		'ISRC'       => 'ISRC',
		'DISCTOTAL'  => 'Total Number of Discs',
		'DISCNUMBER' => 'Disc Number',
		'TRACKNUMBER'=> 'Track Number',
		'TRACKTOTAL' => 'Total Number of Tracks',

	};
}

=head1 op_edit_tags

This is the sub responsible for the non-batch tag editor.  It creates
the editwin, which has three containers and several fields inside.

=cut

sub op_edit_tags {

	$state->{temp}{edit_index} = 0 unless exists $state->{temp}{edit_index};
	my $i = $state->{temp}{edit_index};

	$state->{root}->delete('mainwin');
	$state->{root}->delete('editwin');

	$state->{editwin} = $state->{root}->add(
		'editwin', 'Window',
		-title => "Tag Editor",
		-border => 1,
		-pad => 1,
		-bg => 'blue',
		-fg => 'white',
	);

	_print_track_and_disc_containers(1,1,$i);

	my $ypos = 4;
	my $xpos = 1;

	$state->{editwin_filename} = $state->{editwin}->add(
		'editwin_filename', 'TextEditor',
		-readonly => 1,
		-singleline => 1,
		-padright => 3,
		-border => 1,
		-title => "File name and path",
		-fg => 'red',
		-bg => 'white',
		-focusable => 0,
		-x => $xpos,
		-y => $ypos,
	);

	$ypos += 3;

	$state->{editwin_title} = $state->{editwin}->add(
		'editwin_title', 'TextEditor',
		-singleline => 1,
		-x => $xpos,
		-y => $ypos,
		-border => 1,
		-padright => 3,
		-title => "Track Title",
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_title' },
	);

	$ypos += 3;
	
	$state->{editwin_album} = $state->{editwin}->add(
		'editwin_album', 'TextEditor',
		-singleline => 1,
		-border => 1,
		-title => 'Album Name',
		-x => $xpos,
		-padright => 3,
		-y => $ypos,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_album' },
	);

	$ypos += 3;
	
	$state->{editwin_artist} = $state->{editwin}->add(
		'editwin_artist', 'TextEditor',
		-singleline => 1,
		-border => 1,
		-padright => 3,
		-title => 'Artist Name',
		-x => $xpos,
		-y => $ypos,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_artist' },
	);

	$ypos += 3;
	
	$state->{editwin_albumartist} = $state->{editwin}->add(
		'editwin_albumartist', 'TextEditor',
		-undolevels => 0,
		-singleline => 1,
		-padright => 3,
		-border => 1,
		-title => 'Album Artist Name',
		-x => $xpos,
		-y => $ypos,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_albumartist' },
	);

	$ypos += 3;

	$state->{editwin_keys} = $state->{editwin}->add(
		'label_keys', 'Label',
		-border => 1,
		-title => 'Command Reference',
		-text => 
	"^P - Save and Previous    ^N - Save and Next   ^Z - Undo (within field)\n".
	"ESC - Main Menu (without saving)   ^S - Save   ^I - Apply this value to all",
		-x => $xpos,
		-y => $ypos,
		-padright => 3,
		-bg => 'gray',
		-fg => 'magenta',
		-bold => 1,
	);

	$ypos += 4;

	#$state->{editwin_messages} = $state->{editwin}->add(
	#	'editwin_messages', 'Label',
	#	-border => 1,
	#	-title => 'Status',
	#	-x => $xpos,
	#	-y => $ypos,
	#	-padright => 3,
	#	-bg => 'gray',
	#	-fg => 'green',
	#	-bold => 1,
	#);

	_update_field_values();

	$state->{editwin}->set_binding(
		sub {
			$state->{root}->delete('editwin'); 
			op_mm();
		}, 
		Curses::UI::Common::CUI_ESCAPE()
	);
	
	$state->{editwin}->set_binding(
		sub {
			if($state->{temp}{edit_index} ne 0) {
				_editwin_save();
				_editwin_status("metadata saved.");
				$state->{temp}{edit_index}--;
				op_edit_tags();
			}
		},
		"\cP"
	);

	$state->{editwin}->set_binding(
		sub {
			if($state->{temp}{edit_index} ne @$files - 1) {
				_editwin_save();
				_editwin_status("metadata saved.");
				$state->{temp}{edit_index}++;
				op_edit_tags();
			}
		},
		"\cN"
	);

	$state->{editwin}->set_binding(
		sub {
				_editwin_save();
				$cui->status("Saved.");
				sleep 1;
				$cui->nostatus;
				_editwin_status("metadata saved.");
		},
		"\cS"
	);

	$state->{temp}{lastfield} = 'editwin_title'
		unless(defined($state->{temp}{lastfield}));	
	$state->{$state->{temp}{lastfield}}->focus();

}

sub _editwin_status {
	#my $msg = shift;
	#my $t = time();
	#$state->{editwin_messages}->text($t . ": " . $msg);
}

sub _editwin_save {
	my $ul = _update_list();
	my $file = $files->[$state->{temp}{edit_index}];

	foreach my $field (@$ul) {
		my $val = $state->{'editwin_' . $field}->get();
		$file->write_tag($field,$val);
	}
}

sub _update_list {
	[ qw(
		tracknum 	
		totaltracknums	
		discnum		
		totaldiscnums	
		year		
		genre		
		title		
		album		
		artist		
		albumartist	
	) ];
}

=head1 _update_field_values

this gets called each time we go forward/back in the single-file tag editor
to replace the text in the textboxes with the actual metadata from the current
file.

=cut

sub _update_field_values {
	my $i = $state->{temp}{edit_index};
	$state->{editwin_filenum}->text( $i + 1 );
	$state->{editwin_totalfilenums}->text( @$files + 0 );
	$state->{editwin_filename}->text( $files->[$i]{filename} );
	my $f = _update_list();
	foreach my $k (@$f) {
		$state->{'editwin_'. $k}->text( $files->[$i]->read_tag($k) );
		$state->{'editwin_'. $k}->pos(999);
	}
}

sub _pretty_die {
	my $msg = shift;
	$cui->error($msg);
	my_exit(127);
}

sub _print_track_and_disc_containers {
	my ($xpos,$ypos,$i) = @_;
	
	$state->{editwin_filenums} = $state->{editwin}->add(
		'container_filenums', 'Container',
		-title => "File",
		-x => $xpos,
		-y => $ypos,
		-height => 1,
		-width => 12,
		-border => 1,
		-releasefocus => 1,
		-focusable => 0,
	);

	$state->{editwin_filenum} = $state->{editwin_filenums}->add(
		'textbox_filenum', 'TextEditor',
		-singleline => 1,
		-readonly => 1,
		-focusable => 0,
		-border => 0,
		-width => 3,
		-fg => 'red',
		-bg => 'white',
	);

	$state->{editwin_filenum_of} = $state->{editwin_filenums}->add(
		'label_filenum_of', 'Label',
		-text => ' of ',
		-x => 3,
		-fg => 'red',
	);

	$state->{editwin_totalfilenums} = $state->{editwin_filenums}->add(
		'textbox_totalfilenums', 'TextEditor',
		-singleline => 1,
		-readonly => 1,
		-border => 0,
		-x => 7,
		-width => 3,
		-fg => 'red',
		-bg => 'white',
		-focusable => 0,
	);

	$xpos += 12;
	
	$state->{editwin_tracknums} = $state->{editwin}->add(
		'container_tracknums', 'Container',
		-title => "Track",
		-x => $xpos,
		-y => $ypos,
		-height => 1,
		-width => 12,
		-border => 1,
		-releasefocus => 1,
	);

	$state->{editwin_tracknum} = $state->{editwin_tracknums}->add(
		'editwin_tracknum', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 0,
		-width => 3,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_tracknum' },
	);

	$state->{editwin_tracknum_of} = $state->{editwin_tracknums}->add(
		'label_tracknum_of', 'Label',
		-text => ' of ',
		-x => 3,
		-fg => 'red',
	);

	$state->{editwin_totaltracknums} = $state->{editwin_tracknums}->add(
		'editwin_totaltracknums', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 0,
		-x => 7,
		-width => 3,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_totaltracknums' },
	);

	$xpos += 12;

	$state->{editwin_discnums} = $state->{editwin}->add(
		'container_discnums', 'Container',
		-title => "Disc",
		-x => $xpos,
		-y => $ypos,
		-height => 1,
		-width => 12,
		-border => 1,
		-releasefocus => 1,
	);

	$state->{editwin_discnum} = $state->{editwin_discnums}->add(
		'editwin_discnum', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 0,
		-width => 3,
		-fg => 'blue',
		-onfocus => sub { },
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_discnum' },
	);

	$state->{editwin_discnum_of} = $state->{editwin_discnums}->add(
		'label_discnum_of', 'Label',
		-text => ' of ',
		-x => 3,
		-fg => 'red',
	);

	$state->{editwin_totaldiscnums} = $state->{editwin_discnums}->add(
		'editwin_totaldiscnums', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 0,
		-x => 7,
		-width => 3,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_totaldiscnums' },
	);

	$xpos += 12;

	$state->{editwin_year} = $state->{editwin}->add(
		'editwin_year', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 1,
		-title => "Year",
		-x => $xpos,
		-y => $ypos,
		-width => 14,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_year' },
	);

	$xpos += 14;

	$state->{editwin_genre} = $state->{editwin}->add(
		'editwin_genre', 'TextEditor',
		-singleline => 1,
		-undolevels => 0,
		-border => 1,
		-padright => 3,
		-title => "Genre",
		-x => $xpos,
		-y => $ypos,
		-fg => 'blue',
		-bg => 'white',
		-pos => 999,
		-onfocus => sub { $state->{temp}{lastfield} = 'editwin_genre' },
	);

}

sub files_displaynames {
	my $out = [];
	foreach my $file (@$files) {
		push(@$out,$file->displayname);
	}
	return $out;
}

sub _selection_size {
	my $tot = 0;
	$tot += $_->filesize foreach (@$files);
	return $tot;
}

sub aboutbox {
	$cui->dialog(
		-message => 
			"tedit v$VERSION by sneak <sneak\@datavibe.net>\n" .
			"http://sneak.datavibe.net/2009/09/tedit.php",
	);
}
	
sub notimpl {
	$cui->error("Not implemented yet, sorry.");
	my_exit(127);
}

sub fileselect_cwd {
	fileselect_dir('.');
}

sub fileselect_dir {
	my $dir = shift;
	
	my $crit = 
		File::Find::Rule
			->extras( { follow => 0 } )
			->maxdepth(1)
			->name(qr/\.flac$|\.mp3$/i);


	my @out = sort $crit->in($dir);
	
	$files = [];
	foreach my $file (@out) {
		my $newobj = TEdit::MediaFile->new({
			filename => $dir . '/' . $file,
		});
		push(@$files,$newobj) unless $newobj->error;
	}

	$cui->error("No suitable files found.")
		unless(@$files);

}

sub size_prettyprint {
	my $size = shift;
	my %h = _si_prefixes();
	my @prefixes = sort { $b <=> $a } keys(%h);
	foreach my $prefix (@prefixes) {
		if ($size >= $prefix) {
			return(
				sprintf(
					"%0.0d %s%s",
					($size / $prefix),
					($h{$prefix}),
					'B'
				)
			);
				
		}
	}
}

sub _si_prefixes {
	( $si_units ?
		(
			0	=> '',
			10**3	=> 'K',
			10**6 	=> 'M',
			10**9 	=> 'G',
		)
	:
		(
			0	=> '',
			2**10	=> 'Ki',
			2**20 	=> 'Mi',
			2**30 	=> 'Gi',
		)
	) ;
};

sub my_exit {
	my $code = shift || 0;
	exit($code);
}

sub _menubar {
	[
		{
			-label => 'File',
			-submenu => [
				{
					-label => 'Scan current directory',
					-value => \&fileselect_cwd,
				},
				{
					-label => 'Scan other directory',
					-value => \&notimpl,
				},
				{
					-label => 'Exit    ^C',
					-value => \&my_exit,
				}
			]
		},
		{
			-label => 'Operations',
			-submenu => [
				{
					-label => 'Edit Single File Tags',
					-value => sub { $state->{filelist}->focus()},
				},
				{
					-label => 'Batch Edit File Tags',
					-value => \&op_batch_edit,
				},
				{
					-label => 'Reorder Files',
					-value => \&op_reorder,
				},
				{
					-label => 'Apply Current Tracknumbering',
					-value => \&op_tracknumber,
				},
				{
					-label => 'Rename Files From Tags',
					-value => \&op_rename_from_tags,
				},

			],
		},
		{
			-label => 'About',
			-submenu => [
				{
					-label => "About tedit v$VERSION",
					-value => \&aboutbox,
				},
			],
		},

	];

}

sub dout {
	return unless defined($debug) && $debug;
	my $msg = shift;
	my @lines = split(/[\r\n]+/,$msg);
	my $when = time();
	foreach my $l (@lines) {
		print STDERR "*** $0 $when: $l\n";
	}
}

=head1 AUTHOR

This program, tedit, was created 14 Sep 2009 by 
Jeffrey Paul C<< <sneak@datavibe.net> >>.  All rights reserved.

=cut

1;

__END__
